package xiongwei.jiang.service.impl;

import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.config.builders.ExpiryPolicyBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.ehcache.config.units.EntryUnit;
import org.ehcache.config.units.MemoryUnit;
import org.ehcache.expiry.ExpiryPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.stereotype.Service;
import xiongwei.jiang.property.EhcacheProperties;
import xiongwei.jiang.service.EhcacheService;
import xiongwei.jiang.util.StringUtils;

import javax.annotation.Resource;
import java.io.File;
import java.time.Duration;
import java.time.temporal.ChronoUnit;

/**
 * 缓存服务接口实现类
 *
 * @author 天之蓝
 */
@EnableCaching
@EnableConfigurationProperties(value = EhcacheProperties.class)
@Service
public class EhcacheServiceImpl implements EhcacheService, InitializingBean, DisposableBean {
    private static final Logger LOGGER = LoggerFactory.getLogger(EhcacheServiceImpl.class);

    public static final String DEFAULT_ALIAS = "ehcache:default";

    private CacheConfigurationBuilder<String, String> cacheConfigurationBuilder;

    private ExpiryPolicy<Object, Object> defaultExpiryPolicy;

    private CacheManager cacheManager;

    @Resource
    private EhcacheProperties ehcacheProperties;

    @Override
    public void afterPropertiesSet() {
        initEhcacheProperties();
        ResourcePoolsBuilder resourcePoolsBuilder = ResourcePoolsBuilder.newResourcePoolsBuilder()
                // JVM 内存中缓存的 key 数量
                .heap(ehcacheProperties.getHeap(), EntryUnit.ENTRIES)
                // 堆外内存大小，单位：MB
                .offheap(ehcacheProperties.getOffHeap(), MemoryUnit.MB);
        CacheManagerBuilder<CacheManager> cacheManagerBuilder = CacheManagerBuilder.newCacheManagerBuilder();
        String diskDir = ehcacheProperties.getDiskDir();
        if (StringUtils.isNotBlank(diskDir)) {
            // 持久化到磁盘的大小，单位：MB（当且仅当 diskDir 不为空时生效）
            resourcePoolsBuilder.disk(ehcacheProperties.getDisk(), MemoryUnit.MB, true);
            File file = new File(diskDir);
            // 是否绝对路径
            boolean isAbsolutePath = file.isAbsolute();
            diskDir = file.getPath();
            diskDir = diskDir.startsWith(".") ? diskDir.substring(1) : isAbsolutePath ? diskDir : File.separator + diskDir;
            ehcacheProperties.setDiskDir(isAbsolutePath ? diskDir : System.getProperty("user.dir") + diskDir);
            // 磁盘持久化目录（支持相对路径，若值为空，则不进行持久化）
            cacheManagerBuilder.with(CacheManagerBuilder.persistence(ehcacheProperties.getDiskDir()));
        }
        // 缓存数据 K 和 V 的数据类型，在 ehcache3.3+ 中必须指定缓存键值类型（If these differ from the ones we expect, the CacheManager throws a ClassCastException early in the application’s lifecycle.）
        cacheConfigurationBuilder = CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, String.class, resourcePoolsBuilder);
        // ttl 缓存过期时间（timeToLiveSeconds）
        defaultExpiryPolicy = ehcacheProperties.getTtl() == 0L ? ExpiryPolicyBuilder.noExpiration() : ExpiryPolicyBuilder.timeToLiveExpiration(Duration.of(ehcacheProperties.getTtl(), ChronoUnit.SECONDS));
        cacheManager = cacheManagerBuilder.withCache(DEFAULT_ALIAS, cacheConfigurationBuilder.withExpiry(defaultExpiryPolicy).build()).build();
        cacheManager.init();
        LOGGER.debug(ehcacheProperties.toString());
    }

    private void initEhcacheProperties() {
        if (ehcacheProperties.getHeap() == null) {
            ehcacheProperties.setHeap(1024L);
        }
        if (ehcacheProperties.getOffHeap() == null) {
            ehcacheProperties.setOffHeap(128L);
        }
        if (StringUtils.isNotBlank(ehcacheProperties.getDiskDir()) && ehcacheProperties.getDisk() == null) {
            ehcacheProperties.setDisk(512L);
        }
        if (ehcacheProperties.getTtl() == null || ehcacheProperties.getTtl() < 0L) {
            ehcacheProperties.setTtl(7200L);
        }
    }

    @Override
    public void destroy() {
        if (cacheManager != null) {
            cacheManager.close();
        }
    }

    @Override
    public Cache<String, String> getStringCache(String alias) {
        Cache<String, String> cache;
        try {
            cache = cacheManager.getCache(alias, String.class, String.class);
        } catch (Exception e) {
            LOGGER.error("获取字符串缓存失败：alias = {}", alias);
            return null;
        }
        LOGGER.debug("获取字符串缓存成功：alias = {} and cache = {}", alias, cache);
        return cache;
    }

    @Override
    public String getStringCache(String alias, String key) {
        Cache<String, String> cache;
        try {
            cache = cacheManager.getCache(alias, String.class, String.class);
        } catch (Exception e) {
            LOGGER.error("获取字符串缓存失败：alias = {} and key = {}", alias, key);
            return null;
        }
        if (cache == null) {
            LOGGER.error("获取字符串缓存失败：alias = {} and key = {}", alias, key);
            return null;
        }
        String value = cache.get(key);
        LOGGER.debug("获取字符串缓存成功：alias = {} and key = {} and value = {}", alias, key, value);
        return value;
    }

    @Override
    public Cache<String, String> setStringCache(String alias, String key, String value) {
        return setStringCache(alias, key, value, null);
    }

    @Override
    public Cache<String, String> setStringCache(String alias, String key, String value, Long ttl) {
        Cache<String, String> cache = null;
        boolean isNeedBuild;
        try {
            cache = cacheManager.getCache(alias, String.class, String.class);
            isNeedBuild = cache == null;
        } catch (Exception e) {
            isNeedBuild = true;
        }
        if (isNeedBuild) {
            ExpiryPolicy<Object, Object> expiryPolicy = ttl == null ? defaultExpiryPolicy : ttl == 0L ? ExpiryPolicyBuilder.noExpiration() : ExpiryPolicyBuilder.timeToLiveExpiration(Duration.of(ttl, ChronoUnit.SECONDS));
            cache = cacheManager.createCache(alias, cacheConfigurationBuilder.withExpiry(expiryPolicy).build());
        }
        cache.put(key, value);
        LOGGER.debug("设置字符串缓存成功：alias = {} and key = {} and value = {} and ttl = {}", alias, key, value, ttl == null ? ehcacheProperties.getTtl() : ttl);
        return cache;
    }

    @Override
    public boolean deleteCache(String alias) {
        try {
            cacheManager.removeCache(alias);
            LOGGER.debug("删除字符串缓存成功：alias = {}", alias);
            return true;
        } catch (Exception e) {
            LOGGER.error("删除字符串缓存失败：alias = {}", alias);
            return false;
        }
    }
}
